#!/usr/bin/env python  
#encoding: utf-8  
#description: 一个守护进程的简单包装类, 具备常用的start|stop|restart|status功能, 使用方便  
#             需要改造为守护进程的程序只需要重写基类的run函数就可以了  
#date: 2015-10-29  
#usage: 启动: python daemon_class.py start  
#       关闭: python daemon_class.py stop  
#       状态: python daemon_class.py status  
#       重启: python daemon_class.py restart  
#       查看: ps -axj | grep daemon_class  

import atexit, os, sys, time, signal  

class CDaemon:
    '''
    a generic daemon class. 
    usage: subclass the CDaemon class and override the run() method
    verbose 表示将启动运行过程中的异常错误信息打印到终端,便于调试,建议非调试模式下关闭, 默认为1, 表示开启 
    pid_path 表示守护进程pid文件的绝对路径 
    '''
    def __init__(self, name, pid_path, home_dir='.', umask=22, verbose=1):
        self.name = name
        self.pidfile = pid_path #pid文件绝对路径  
        self.home_dir = home_dir  
        self.verbose = verbose #调试开关  
        self.umask = umask  
        self.daemon_alive = True  

    def daemonize(self):
        try:
            pid = os.fork()
            if pid > 0:
                sys.exit(0)
        except OSError as e:
            sys.stderr.write('fork #1 failed: %d (%s)\n' % (e.errno, e.strerror))
            sys.exit(1)
        os.chdir(self.home_dir)
        os.setsid()
        os.umask(self.umask)
        try:
            pid = os.fork()
            if pid > 0:
                sys.exit(0)
        except OSError as e:
            sys.stderr.write('fork #2 failed: %d (%s)\n' % (e.errno, e.strerror))
            sys.exit(1)
        sys.stdout.flush()
        sys.stderr.flush()
        '''
        si = open(self.stdin, 'r')
        so = open(self.stdout, 'a+')
        if self.stderr:
            se = open(self.stderr, 'a+')
        else:
            se = so
        os.dup2(si.fileno(), sys.stdin.fileno())
        os.dup2(so.fileno(), sys.stdout.fileno())
        os.dup2(se.fileno(), sys.stderr.fileno())
        '''

        def sig_handler(signum, frame):
            self.daemon_alive = False
        signal.signal(signal.SIGTERM, sig_handler)
        signal.signal(signal.SIGINT, sig_handler)
        if self.verbose >= 1:
            print('daemon process started ...')
        atexit.register(self.del_pid)
        pid = str(os.getpid())
        open(self.pidfile, 'w+').write('%s\n' % pid)

    def get_pid(self):
        try:
            pf = open(self.pidfile, 'r')
            pid = int(pf.read().strip())
            pf.close()
        except IOError:
            pid = None
        except SystemExit:
            pid = None
        return pid

    def del_pid(self):
        if os.path.exists(self.pidfile):
            os.remove(self.pidfile)

    def start(self, *args, **kwargs):
        if self.verbose >= 1:
            print('XueQing %s service starting ...' % self.name)
        #check for a pid file to see if the daemon already runs
        pid = self.get_pid()
        if pid:
            msg = 'pid file %s already exists, is it already running?\n'
            sys.stderr.write(msg % self.pidfile)
            sys.exit(1)
        #start the daemon
        self.daemonize()
        self.run(*args, **kwargs)

    def stop(self):
        if self.verbose >= 1:
            print('XueQing %s service stopping ...' % self.name)
        pid = self.get_pid()
        if not pid:
            msg = 'pid file [%s] does not exist. Not running?\n' % self.pidfile
            sys.stderr.write(msg)
            if os.path.exists(self.pidfile):
                os.remove(self.pidfile)
            return
        #try to kill the daemon process  
        try:
            i = 0
            while 1:
                os.kill(pid, signal.SIGTERM)
                time.sleep(0.1)
                i = i + 1
                if i % 10 == 0:
                    os.kill(pid, signal.SIGHUP)
        except OSError as err:
            err = str(err)
            if err.find('No such process') > 0:
                if os.path.exists(self.pidfile):
                    os.remove(self.pidfile)
                if self.verbose >= 1:
                    print('Stopped.')
            else:
                print(str(err))
                sys.exit(1)
            #if self.verbose >= 1:
            #    print('Stopped.')

    def restart(self, *args, **kwargs):
        self.stop()
        self.start(*args, **kwargs)

    def is_running(self):
        pid = self.get_pid()
        #print(pid)
        return pid and os.path.exists('/proc/%d' % pid)

    def run(self, *args, **kwargs):  
        #'NOTE: override the method in subclass'  
        print('base class run()')

